From 9926e6ebde28956ae65e5127966fd740552fbe6a Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Fri, 26 Jul 2019 10:40:43 -0700 Subject: [PATCH] textlayout: introduce caching for GtkTextLineDisplay This adds a GtkTextLineDisplayCache which can be used to cache a number of GtkTextLineDisplay (and thus, PangoLayout) while displaying a view. It uses a GSequence to track the position of the GtkTextLineDisplay relative to each other, a MRU to cull the least recently used display, and and a direct hashtable to lookup display by GtkTextLine. We only cache lines that are to be displayed (!size_only). We may want to either create a second collection of "size_only" lines to speed that up, or determine that it is unnecessary (which is likely the case). --- gtk/gtktextlayout.c | 468 +++++++++-------- gtk/gtktextlayoutprivate.h | 25 +- gtk/gtktextlinedisplaycache.c | 716 +++++++++++++++++++++++++++ gtk/gtktextlinedisplaycacheprivate.h | 59 +++ gtk/meson.build | 1 + 5 files changed, 1051 insertions(+), 218 deletions(-) create mode 100644 gtk/gtktextlinedisplaycache.c create mode 100644 gtk/gtktextlinedisplaycacheprivate.h diff --git a/gtk/gtktextlayout.c b/gtk/gtktextlayout.c index 3196db22f8..2ee27ed762 100644 --- a/gtk/gtktextlayout.c +++ b/gtk/gtktextlayout.c @@ -82,6 +82,7 @@ #include "gtktextbtree.h" #include "gtktextbufferprivate.h" #include "gtktextiterprivate.h" +#include "gtktextlinedisplaycacheprivate.h" #include "gtktextutil.h" #include "gskpango.h" #include "gtkintl.h" @@ -101,6 +102,9 @@ struct _GtkTextLayoutPrivate direction only influences the direction of the cursor line. */ GtkTextLine *cursor_line; + + /* Cache for GtkTextLineDisplay to reduce overhead creating layouts */ + GtkTextLineDisplayCache *cache; }; static GtkTextLineData *gtk_text_layout_real_wrap (GtkTextLayout *layout, @@ -133,19 +137,33 @@ static void gtk_text_layout_invalidate_all (GtkTextLayout *layout); static PangoAttribute *gtk_text_attr_appearance_new (const GtkTextAppearance *appearance); -static void gtk_text_layout_mark_set_handler (GtkTextBuffer *buffer, - const GtkTextIter *location, - GtkTextMark *mark, - gpointer data); -static void gtk_text_layout_buffer_insert_text (GtkTextBuffer *textbuffer, - GtkTextIter *iter, - gchar *str, - gint len, - gpointer data); -static void gtk_text_layout_buffer_delete_range (GtkTextBuffer *textbuffer, - GtkTextIter *start, - GtkTextIter *end, - gpointer data); +static void gtk_text_layout_after_mark_set_handler (GtkTextBuffer *buffer, + const GtkTextIter *location, + GtkTextMark *mark, + gpointer data); +static void gtk_text_layout_after_buffer_insert_text (GtkTextBuffer *textbuffer, + GtkTextIter *iter, + gchar *str, + gint len, + gpointer data); +static void gtk_text_layout_after_buffer_delete_range (GtkTextBuffer *textbuffer, + GtkTextIter *start, + GtkTextIter *end, + gpointer data); +static void gtk_text_layout_before_mark_set_handler (GtkTextBuffer *buffer, + const GtkTextIter *location, + GtkTextMark *mark, + gpointer data); +static void gtk_text_layout_before_buffer_insert_text (GtkTextBuffer *textbuffer, + GtkTextIter *iter, + gchar *str, + gint len, + gpointer data); +static void gtk_text_layout_before_buffer_delete_range (GtkTextBuffer *textbuffer, + GtkTextIter *start, + GtkTextIter *end, + gpointer data); + static void gtk_text_layout_update_cursor_line (GtkTextLayout *layout); @@ -182,9 +200,10 @@ G_DEFINE_TYPE_WITH_PRIVATE (GtkTextLayout, gtk_text_layout, G_TYPE_OBJECT) static void gtk_text_layout_dispose (GObject *object) { - GtkTextLayout *layout; + GtkTextLayout *layout = GTK_TEXT_LAYOUT (object); + GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout); - layout = GTK_TEXT_LAYOUT (object); + g_clear_pointer (&priv->cache, gtk_text_line_display_cache_free); gtk_text_layout_set_buffer (layout, NULL); @@ -197,8 +216,6 @@ gtk_text_layout_dispose (GObject *object) g_clear_object (&layout->ltr_context); g_clear_object (&layout->rtl_context); - g_clear_pointer (&layout->one_display_cache, gtk_text_line_display_unref); - if (layout->preedit_attrs != NULL) { pango_attr_list_unref (layout->preedit_attrs); @@ -275,7 +292,10 @@ gtk_text_layout_class_init (GtkTextLayoutClass *klass) static void gtk_text_layout_init (GtkTextLayout *text_layout) { + GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (text_layout); + text_layout->cursor_visible = TRUE; + priv->cache = gtk_text_line_display_cache_new (); } GtkTextLayout* @@ -315,14 +335,24 @@ gtk_text_layout_set_buffer (GtkTextLayout *layout, _gtk_text_btree_remove_view (_gtk_text_buffer_get_btree (layout->buffer), layout); - g_signal_handlers_disconnect_by_func (layout->buffer, - G_CALLBACK (gtk_text_layout_mark_set_handler), + g_signal_handlers_disconnect_by_func (layout->buffer, + G_CALLBACK (gtk_text_layout_after_mark_set_handler), + layout); + g_signal_handlers_disconnect_by_func (layout->buffer, + G_CALLBACK (gtk_text_layout_after_buffer_insert_text), + layout); + g_signal_handlers_disconnect_by_func (layout->buffer, + G_CALLBACK (gtk_text_layout_after_buffer_delete_range), + layout); + + g_signal_handlers_disconnect_by_func (layout->buffer, + G_CALLBACK (gtk_text_layout_before_mark_set_handler), layout); - g_signal_handlers_disconnect_by_func (layout->buffer, - G_CALLBACK (gtk_text_layout_buffer_insert_text), + g_signal_handlers_disconnect_by_func (layout->buffer, + G_CALLBACK (gtk_text_layout_before_buffer_insert_text), layout); - g_signal_handlers_disconnect_by_func (layout->buffer, - G_CALLBACK (gtk_text_layout_buffer_delete_range), + g_signal_handlers_disconnect_by_func (layout->buffer, + G_CALLBACK (gtk_text_layout_before_buffer_delete_range), layout); g_object_unref (layout->buffer); @@ -339,11 +369,18 @@ gtk_text_layout_set_buffer (GtkTextLayout *layout, /* Bind to all signals that move the insert mark. */ g_signal_connect_after (layout->buffer, "mark-set", - G_CALLBACK (gtk_text_layout_mark_set_handler), layout); + G_CALLBACK (gtk_text_layout_after_mark_set_handler), layout); g_signal_connect_after (layout->buffer, "insert-text", - G_CALLBACK (gtk_text_layout_buffer_insert_text), layout); + G_CALLBACK (gtk_text_layout_after_buffer_insert_text), layout); g_signal_connect_after (layout->buffer, "delete-range", - G_CALLBACK (gtk_text_layout_buffer_delete_range), layout); + G_CALLBACK (gtk_text_layout_after_buffer_delete_range), layout); + + g_signal_connect (layout->buffer, "mark-set", + G_CALLBACK (gtk_text_layout_before_mark_set_handler), layout); + g_signal_connect (layout->buffer, "insert-text", + G_CALLBACK (gtk_text_layout_before_buffer_insert_text), layout); + g_signal_connect (layout->buffer, "delete-range", + G_CALLBACK (gtk_text_layout_before_buffer_delete_range), layout); gtk_text_layout_update_cursor_line (layout); } @@ -627,46 +664,26 @@ gtk_text_layout_emit_changed (GtkTextLayout *layout, g_signal_emit (layout, signals[CHANGED], 0, y, old_height, new_height); } -static void -text_layout_changed (GtkTextLayout *layout, - gint y, - gint old_height, - gint new_height, - gboolean cursors_only) -{ - /* Check if the range intersects our cached line display, - * and invalidate the cached line if so. - */ - if (layout->one_display_cache) - { - GtkTextLine *line = layout->one_display_cache->line; - gint cache_y = _gtk_text_btree_find_line_top (_gtk_text_buffer_get_btree (layout->buffer), - line, layout); - gint cache_height = layout->one_display_cache->height; - - if (cache_y + cache_height > y && cache_y < y + old_height) - gtk_text_layout_invalidate_cache (layout, line, cursors_only); - } - - gtk_text_layout_emit_changed (layout, y, old_height, new_height); -} - void gtk_text_layout_changed (GtkTextLayout *layout, gint y, gint old_height, gint new_height) { - text_layout_changed (layout, y, old_height, new_height, FALSE); + GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout); + gtk_text_line_display_cache_invalidate_y_range (priv->cache, layout, y, old_height, FALSE); + gtk_text_layout_emit_changed (layout, y, old_height, new_height); } void gtk_text_layout_cursors_changed (GtkTextLayout *layout, gint y, - gint old_height, - gint new_height) + gint old_height, + gint new_height) { - text_layout_changed (layout, y, old_height, new_height, TRUE); + GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout); + gtk_text_line_display_cache_invalidate_y_range (priv->cache, layout, y, old_height, TRUE); + gtk_text_layout_emit_changed (layout, y, old_height, new_height); } void @@ -819,20 +836,16 @@ gtk_text_layout_invalidate_cache (GtkTextLayout *layout, GtkTextLine *line, gboolean cursors_only) { - if (layout->one_display_cache && line == layout->one_display_cache->line) + GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout); + + g_assert (GTK_IS_TEXT_LAYOUT (layout)); + + if (priv->cache != NULL) { if (cursors_only) - { - GtkTextLineDisplay *display = layout->one_display_cache; - - g_clear_pointer (&display->cursors, g_array_unref); - display->cursors_invalid = TRUE; - display->has_block_cursor = FALSE; - } + gtk_text_line_display_cache_invalidate_cursors (priv->cache, line); else - { - g_clear_pointer (&layout->one_display_cache, gtk_text_line_display_unref); - } + gtk_text_line_display_cache_invalidate_line (priv->cache, line); } } @@ -840,7 +853,7 @@ gtk_text_layout_invalidate_cache (GtkTextLayout *layout, */ static void gtk_text_layout_invalidate_cursor_line (GtkTextLayout *layout, - gboolean cursors_only) + gboolean cursors_only) { GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout); GtkTextLineData *line_data; @@ -849,22 +862,20 @@ gtk_text_layout_invalidate_cursor_line (GtkTextLayout *layout, return; line_data = _gtk_text_line_get_data (priv->cursor_line, layout); - if (line_data) + + if (line_data != NULL) { - if (cursors_only) - gtk_text_layout_invalidate_cache (layout, priv->cursor_line, TRUE); - else - { - gtk_text_layout_invalidate_cache (layout, priv->cursor_line, FALSE); - _gtk_text_line_invalidate_wrap (priv->cursor_line, line_data); - } + gtk_text_layout_invalidate_cache (layout, priv->cursor_line, cursors_only); + + if (!cursors_only) + _gtk_text_line_invalidate_wrap (priv->cursor_line, line_data); gtk_text_layout_invalidated (layout); } } static void -gtk_text_layout_update_cursor_line(GtkTextLayout *layout) +gtk_text_layout_update_cursor_line (GtkTextLayout *layout) { GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout); GtkTextIter iter; @@ -873,6 +884,8 @@ gtk_text_layout_update_cursor_line(GtkTextLayout *layout) gtk_text_buffer_get_insert (layout->buffer)); priv->cursor_line = _gtk_text_iter_get_text_line (&iter); + + gtk_text_line_display_cache_set_cursor_line (priv->cache, priv->cursor_line); } static void @@ -924,34 +937,8 @@ gtk_text_layout_real_invalidate_cursors (GtkTextLayout *layout, const GtkTextIter *start, const GtkTextIter *end) { - /* Check if the range intersects our cached line display, - * and invalidate the cached line if so. - */ - if (layout->one_display_cache) - { - GtkTextIter line_start, line_end; - GtkTextLine *line = layout->one_display_cache->line; - - gtk_text_layout_get_iter_at_line (layout, &line_start, line, 0); - - line_end = line_start; - if (!gtk_text_iter_ends_line (&line_end)) - gtk_text_iter_forward_to_line_end (&line_end); - - if (gtk_text_iter_compare (start, end) > 0) - { - const GtkTextIter *tmp = start; - start = end; - end = tmp; - } - - if (gtk_text_iter_compare (&line_start, end) <= 0 && - gtk_text_iter_compare (start, &line_end) <= 0) - { - gtk_text_layout_invalidate_cache (layout, line, TRUE); - } - } - + GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout); + gtk_text_line_display_cache_invalidate_range (priv->cache, layout, start, end, TRUE); gtk_text_layout_invalidated (layout); } @@ -2122,10 +2109,10 @@ add_preedit_attrs (GtkTextLayout *layout, /* Iterate over the line and fill in display->cursors. * It’s a stripped copy of gtk_text_layout_get_line_display() */ -static void -update_text_display_cursors (GtkTextLayout *layout, - GtkTextLine *line, - GtkTextLineDisplay *display) +void +gtk_text_layout_update_display_cursors (GtkTextLayout *layout, + GtkTextLine *line, + GtkTextLineDisplay *display) { GtkTextLineSegment *seg; GtkTextIter iter; @@ -2275,9 +2262,9 @@ tags_array_toggle_tag (GPtrArray *array, } GtkTextLineDisplay * -gtk_text_layout_get_line_display (GtkTextLayout *layout, - GtkTextLine *line, - gboolean size_only) +gtk_text_layout_create_display (GtkTextLayout *layout, + GtkTextLine *line, + gboolean size_only) { GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout); GtkTextLineDisplay *display; @@ -2305,26 +2292,10 @@ gtk_text_layout_get_line_display (GtkTextLayout *layout, g_return_val_if_fail (line != NULL, NULL); - if (layout->one_display_cache) - { - if (line == layout->one_display_cache->line && - (size_only || !layout->one_display_cache->size_only)) - { - if (!size_only) - update_text_display_cursors (layout, line, layout->one_display_cache); - return gtk_text_line_display_ref (layout->one_display_cache); - } - else - { - g_clear_pointer (&layout->one_display_cache, gtk_text_line_display_unref); - } - } - - DV (g_print ("creating one line display cache (%s)\n", G_STRLOC)); - display = g_rc_box_new0 (GtkTextLineDisplay); - display->size_only = size_only; + display->mru_link.data = display; + display->size_only = !!size_only; display->line = line; display->insert_index = -1; @@ -2335,7 +2306,7 @@ gtk_text_layout_get_line_display (GtkTextLayout *layout, if (totally_invisible_line (layout, line, &iter)) { display->layout = pango_layout_new (layout->ltr_context); - return display; + return g_steal_pointer (&display); } /* Find the bidi base direction */ @@ -2347,7 +2318,7 @@ gtk_text_layout_get_line_display (GtkTextLayout *layout, line->dir_strong == PANGO_DIRECTION_NEUTRAL) { base_dir = (layout->keyboard_direction == GTK_TEXT_DIR_LTR) ? - PANGO_DIRECTION_LTR : PANGO_DIRECTION_RTL; + PANGO_DIRECTION_LTR : PANGO_DIRECTION_RTL; } /* Allocate space for flat text for buffer @@ -2371,7 +2342,7 @@ gtk_text_layout_get_line_display (GtkTextLayout *layout, seg->type == >k_text_child_type) { style = get_style (layout, tags); - initial_toggle_segments = FALSE; + initial_toggle_segments = FALSE; /* We have to delay setting the paragraph values until we * hit the first texture or text segment because toggles at @@ -2401,9 +2372,9 @@ gtk_text_layout_get_line_display (GtkTextLayout *layout, */ gint bytes = 0; - GtkTextLineSegment *prev_seg = NULL; + GtkTextLineSegment *prev_seg = NULL; - while (seg) + while (seg) { if (seg->type == >k_text_char_type) { @@ -2412,35 +2383,35 @@ gtk_text_layout_get_line_display (GtkTextLayout *layout, buffer_byte_offset += seg->byte_count; bytes += seg->byte_count; } - else if (seg->type == >k_text_right_mark_type || - seg->type == >k_text_left_mark_type) + else if (seg->type == >k_text_right_mark_type || + seg->type == >k_text_left_mark_type) { - /* If we have preedit string, break out of this loop - we'll almost - * certainly have different attributes on the preedit string - */ - - if (layout->preedit_len > 0 && - _gtk_text_btree_mark_is_insert (_gtk_text_buffer_get_btree (layout->buffer), - seg->body.mark.obj)) - break; - - if (seg->body.mark.visible) - { - cursor_byte_offsets = g_slist_prepend (cursor_byte_offsets, GINT_TO_POINTER (layout_byte_offset)); - cursor_segs = g_slist_prepend (cursor_segs, seg); - if (_gtk_text_btree_mark_is_insert (_gtk_text_buffer_get_btree (layout->buffer), - seg->body.mark.obj)) - display->insert_index = layout_byte_offset; - } + /* If we have preedit string, break out of this loop - we'll almost + * certainly have different attributes on the preedit string + */ + + if (layout->preedit_len > 0 && + _gtk_text_btree_mark_is_insert (_gtk_text_buffer_get_btree (layout->buffer), + seg->body.mark.obj)) + break; + + if (seg->body.mark.visible) + { + cursor_byte_offsets = g_slist_prepend (cursor_byte_offsets, GINT_TO_POINTER (layout_byte_offset)); + cursor_segs = g_slist_prepend (cursor_segs, seg); + if (_gtk_text_btree_mark_is_insert (_gtk_text_buffer_get_btree (layout->buffer), + seg->body.mark.obj)) + display->insert_index = layout_byte_offset; + } } - else - break; + else + break; - prev_seg = seg; + prev_seg = seg; seg = seg->next; } - seg = prev_seg; /* Back up one */ + seg = prev_seg; /* Back up one */ add_generic_attrs (layout, &style->appearance, bytes, attrs, layout_byte_offset - bytes, @@ -2503,42 +2474,42 @@ gtk_text_layout_get_line_display (GtkTextLayout *layout, /* Style may have changed, drop our current cached style */ invalidate_cached_style (layout); - /* Add the tag only after we have seen some non-toggle non-mark segment, - * otherwise the tag is already accounted for by _gtk_text_btree_get_tags(). */ - if (!initial_toggle_segments) - tags = tags_array_toggle_tag (tags, seg->body.toggle.info->tag); + /* Add the tag only after we have seen some non-toggle non-mark segment, + * otherwise the tag is already accounted for by _gtk_text_btree_get_tags(). */ + if (!initial_toggle_segments) + tags = tags_array_toggle_tag (tags, seg->body.toggle.info->tag); } /* Marks */ else if (seg->type == >k_text_right_mark_type || seg->type == >k_text_left_mark_type) { - gint cursor_offset = 0; - - /* At the insertion point, add the preedit string, if any */ - - if (_gtk_text_btree_mark_is_insert (_gtk_text_buffer_get_btree (layout->buffer), - seg->body.mark.obj)) - { - display->insert_index = layout_byte_offset; - - if (layout->preedit_len > 0) - { - text_allocated += layout->preedit_len; - text = g_realloc (text, text_allocated); + gint cursor_offset = 0; + + /* At the insertion point, add the preedit string, if any */ + + if (_gtk_text_btree_mark_is_insert (_gtk_text_buffer_get_btree (layout->buffer), + seg->body.mark.obj)) + { + display->insert_index = layout_byte_offset; + + if (layout->preedit_len > 0) + { + text_allocated += layout->preedit_len; + text = g_realloc (text, text_allocated); - style = get_style (layout, tags); - add_preedit_attrs (layout, style, attrs, layout_byte_offset, size_only); - release_style (layout, style); + style = get_style (layout, tags); + add_preedit_attrs (layout, style, attrs, layout_byte_offset, size_only); + release_style (layout, style); - memcpy (text + layout_byte_offset, layout->preedit_string, layout->preedit_len); - layout_byte_offset += layout->preedit_len; + memcpy (text + layout_byte_offset, layout->preedit_string, layout->preedit_len); + layout_byte_offset += layout->preedit_len; /* DO NOT increment the buffer byte offset for preedit */ - cursor_offset = layout->preedit_cursor - layout->preedit_len; - } - } - + cursor_offset = layout->preedit_cursor - layout->preedit_len; + } + } + /* Display visible marks */ @@ -2623,17 +2594,17 @@ gtk_text_layout_get_line_display (GtkTextLayout *layout, gint excess = display->total_width - text_pixel_width; switch (pango_layout_get_alignment (display->layout)) - { - case PANGO_ALIGN_LEFT: + { + case PANGO_ALIGN_LEFT: default: - break; - case PANGO_ALIGN_CENTER: - display->x_offset += excess / 2; - break; - case PANGO_ALIGN_RIGHT: - display->x_offset += excess; - break; - } + break; + case PANGO_ALIGN_CENTER: + display->x_offset += excess / 2; + break; + case PANGO_ALIGN_RIGHT: + display->x_offset += excess; + break; + } } /* Free this if we aren't in a loop */ @@ -2645,19 +2616,31 @@ gtk_text_layout_get_line_display (GtkTextLayout *layout, if (tags != NULL) g_ptr_array_free (tags, TRUE); - g_assert (layout->one_display_cache == NULL); - - layout->one_display_cache = gtk_text_line_display_ref (display); - if (saw_widget) allocate_child_widgets (layout, display); - return display; + return g_steal_pointer (&display); +} + +GtkTextLineDisplay * +gtk_text_layout_get_line_display (GtkTextLayout *layout, + GtkTextLine *line, + gboolean size_only) +{ + GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout); + + return gtk_text_line_display_cache_get (priv->cache, layout, line, size_only); } static void gtk_text_line_display_finalize (GtkTextLineDisplay *display) { + g_assert (display != NULL); + g_assert (display->cache_iter == NULL); + g_assert (display->mru_link.prev == NULL); + g_assert (display->mru_link.next == NULL); + g_assert (display->mru_link.data == display); + g_clear_object (&display->layout); g_clear_pointer (&display->cursors, g_array_unref); } @@ -3784,26 +3767,57 @@ gtk_text_layout_spew (GtkTextLayout *layout) #endif } +static void +gtk_text_layout_before_mark_set_handler (GtkTextBuffer *buffer, + const GtkTextIter *location, + GtkTextMark *mark, + gpointer data) +{ + GtkTextLayout *layout = GTK_TEXT_LAYOUT (data); + GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout); + + if (mark == gtk_text_buffer_get_insert (buffer)) + gtk_text_line_display_cache_set_cursor_line (priv->cache, NULL); +} + /* Catch all situations that move the insertion point. */ static void -gtk_text_layout_mark_set_handler (GtkTextBuffer *buffer, - const GtkTextIter *location, - GtkTextMark *mark, - gpointer data) +gtk_text_layout_after_mark_set_handler (GtkTextBuffer *buffer, + const GtkTextIter *location, + GtkTextMark *mark, + gpointer data) { GtkTextLayout *layout = GTK_TEXT_LAYOUT (data); + GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout); if (mark == gtk_text_buffer_get_insert (buffer)) - gtk_text_layout_update_cursor_line (layout); + { + gtk_text_layout_update_cursor_line (layout); + gtk_text_line_display_cache_set_cursor_line (priv->cache, priv->cursor_line); + } } static void -gtk_text_layout_buffer_insert_text (GtkTextBuffer *textbuffer, - GtkTextIter *iter, - gchar *str, - gint len, - gpointer data) +gtk_text_layout_before_buffer_insert_text (GtkTextBuffer *textbuffer, + GtkTextIter *iter, + gchar *str, + gint len, + gpointer data) +{ + GtkTextLayout *layout = GTK_TEXT_LAYOUT (data); + GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout); + GtkTextLine *line = _gtk_text_iter_get_text_line (iter); + + gtk_text_line_display_cache_invalidate_line (priv->cache, line); +} + +static void +gtk_text_layout_after_buffer_insert_text (GtkTextBuffer *textbuffer, + GtkTextIter *iter, + gchar *str, + gint len, + gpointer data) { GtkTextLayout *layout = GTK_TEXT_LAYOUT (data); @@ -3811,10 +3825,22 @@ gtk_text_layout_buffer_insert_text (GtkTextBuffer *textbuffer, } static void -gtk_text_layout_buffer_delete_range (GtkTextBuffer *textbuffer, - GtkTextIter *start, - GtkTextIter *end, - gpointer data) +gtk_text_layout_before_buffer_delete_range (GtkTextBuffer *textbuffer, + GtkTextIter *start, + GtkTextIter *end, + gpointer data) +{ + GtkTextLayout *layout = GTK_TEXT_LAYOUT (data); + GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout); + + gtk_text_line_display_cache_invalidate_range (priv->cache, layout, start, end, FALSE); +} + +static void +gtk_text_layout_after_buffer_delete_range (GtkTextBuffer *textbuffer, + GtkTextIter *start, + GtkTextIter *end, + gpointer data) { GtkTextLayout *layout = GTK_TEXT_LAYOUT (data); @@ -4041,6 +4067,7 @@ gtk_text_layout_snapshot (GtkTextLayout *layout, const GdkRectangle *clip, float cursor_alpha) { + GtkTextLayoutPrivate *priv; GskPangoRenderer *crenderer; GtkStyleContext *context; gint offset_y; @@ -4055,6 +4082,8 @@ gtk_text_layout_snapshot (GtkTextLayout *layout, g_return_if_fail (layout->buffer != NULL); g_return_if_fail (snapshot != NULL); + priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout); + context = gtk_widget_get_style_context (widget); gtk_style_context_get_color (context, &color); @@ -4155,7 +4184,24 @@ gtk_text_layout_snapshot (GtkTextLayout *layout, gtk_text_layout_wrap_loop_end (layout); + /* Only update eviction source once per snapshot */ + gtk_text_line_display_cache_delay_eviction (priv->cache); + g_slist_free (line_list); gsk_pango_renderer_release (crenderer); } + +gint +gtk_text_line_display_compare (const GtkTextLineDisplay *display1, + const GtkTextLineDisplay *display2, + GtkTextLayout *layout) +{ + GtkTextIter iter1; + GtkTextIter iter2; + + gtk_text_layout_get_iter_at_line (layout, &iter1, display1->line, 0); + gtk_text_layout_get_iter_at_line (layout, &iter2, display2->line, 0); + + return gtk_text_iter_compare (&iter1, &iter2); +} diff --git a/gtk/gtktextlayoutprivate.h b/gtk/gtktextlayoutprivate.h index 7905d2b542..27da15d2bd 100644 --- a/gtk/gtktextlayoutprivate.h +++ b/gtk/gtktextlayoutprivate.h @@ -138,11 +138,6 @@ struct _GtkTextLayout * over long runs with the same style. */ GtkTextAttributes *one_style_cache; - /* A cache of one line display. Getting the same line - * many times in a row is the most common case. - */ - GtkTextLineDisplay *one_display_cache; - /* Whether we are allowed to wrap right now */ gint wrap_loop_count; @@ -223,6 +218,12 @@ struct _GtkTextLineDisplay PangoLayout *layout; GArray *cursors; /* indexes of cursors in the PangoLayout */ + /* GSequenceIter backpointer for use within cache */ + GSequenceIter *cache_iter; + + /* GQueue link for use in MRU to help cull cache */ + GList mru_link; + GtkTextDirection direction; gint width; /* Width of layout */ @@ -298,8 +299,12 @@ void gtk_text_layout_wrap_loop_end (GtkTextLayout *layout); GtkTextLineDisplay* gtk_text_layout_get_line_display (GtkTextLayout *layout, GtkTextLine *line, gboolean size_only); -GtkTextLineDisplay *gtk_text_line_display_ref (GtkTextLineDisplay *display); -void gtk_text_line_display_unref (GtkTextLineDisplay *display); + +GtkTextLineDisplay *gtk_text_line_display_ref (GtkTextLineDisplay *display); +void gtk_text_line_display_unref (GtkTextLineDisplay *display); +gint gtk_text_line_display_compare (const GtkTextLineDisplay *display1, + const GtkTextLineDisplay *display2, + GtkTextLayout *layout); void gtk_text_layout_get_line_at_y (GtkTextLayout *layout, GtkTextIter *target_iter, @@ -354,6 +359,12 @@ void gtk_text_layout_get_cursor_locations (GtkTextLayout *layout, GtkTextIter *iter, GdkRectangle *strong_pos, GdkRectangle *weak_pos); +GtkTextLineDisplay *gtk_text_layout_create_display (GtkTextLayout *layout, + GtkTextLine *line, + gboolean size_only); +void gtk_text_layout_update_display_cursors (GtkTextLayout *layout, + GtkTextLine *line, + GtkTextLineDisplay *display); gboolean _gtk_text_layout_get_block_cursor (GtkTextLayout *layout, GdkRectangle *pos); gboolean gtk_text_layout_clamp_iter_to_vrange (GtkTextLayout *layout, diff --git a/gtk/gtktextlinedisplaycache.c b/gtk/gtktextlinedisplaycache.c new file mode 100644 index 0000000000..6129d787fc --- /dev/null +++ b/gtk/gtktextlinedisplaycache.c @@ -0,0 +1,716 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * Copyright (C) 2019 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "config.h" + +#include "gtktextbtree.h" +#include "gtktextbufferprivate.h" +#include "gtktextiterprivate.h" +#include "gtktextlinedisplaycacheprivate.h" + +#define MRU_MAX_SIZE 250 +#define BLOW_CACHE_TIMEOUT_SEC 20 +#define DEBUG_LINE_DISPLAY_CACHE 0 + +struct _GtkTextLineDisplayCache +{ + GSequence *sorted_by_line; + GHashTable *line_to_display; + GtkTextLine *cursor_line; + GQueue mru; + GSource *evict_source; + +#if DEBUG_LINE_DISPLAY_CACHE + guint log_source; + gint hits; + gint misses; + gint inval; + gint inval_cursors; + gint inval_by_line; + gint inval_by_range; + gint inval_by_y_range; +#endif +}; + +#if DEBUG_LINE_DISPLAY_CACHE +# define STAT_ADD(val,n) ((val) += n) +# define STAT_INC(val) STAT_ADD(val,1) +static gboolean +dump_stats (gpointer data) +{ + GtkTextLineDisplayCache *cache = data; + g_printerr ("%p: size=%u hits=%d misses=%d inval_total=%d " + "inval_cursors=%d inval_by_line=%d " + "inval_by_range=%d inval_by_y_range=%d\n", + cache, g_hash_table_size (cache->line_to_display), + cache->hits, cache->misses, + cache->inval, cache->inval_cursors, + cache->inval_by_line, cache->inval_by_range, + cache->inval_by_y_range); + return G_SOURCE_CONTINUE; +} +#else +# define STAT_ADD(val,n) +# define STAT_INC(val) +#endif + +GtkTextLineDisplayCache * +gtk_text_line_display_cache_new (void) +{ + GtkTextLineDisplayCache *ret; + + ret = g_slice_new0 (GtkTextLineDisplayCache); + ret->sorted_by_line = g_sequence_new ((GDestroyNotify)gtk_text_line_display_unref); + ret->line_to_display = g_hash_table_new (NULL, NULL); + +#if DEBUG_LINE_DISPLAY_CACHE + ret->log_source = g_timeout_add_seconds (1, dump_stats, ret); +#endif + + return g_steal_pointer (&ret); +} + +void +gtk_text_line_display_cache_free (GtkTextLineDisplayCache *cache) +{ +#if DEBUG_LINE_DISPLAY_CACHE + g_clear_handle_id (&cache->log_source, g_source_remove); +#endif + + g_clear_pointer (&cache->evict_source, g_source_destroy); + g_clear_pointer (&cache->sorted_by_line, g_sequence_free); + g_clear_pointer (&cache->line_to_display, g_hash_table_unref); + g_slice_free (GtkTextLineDisplayCache, cache); +} + +static gboolean +gtk_text_line_display_cache_blow_cb (gpointer data) +{ + GtkTextLineDisplayCache *cache = data; + + g_assert (cache != NULL); + +#if DEBUG_LINE_DISPLAY_CACHE + g_printerr ("Evicting GtkTextLineDisplayCache\n"); +#endif + + cache->evict_source = NULL; + + gtk_text_line_display_cache_invalidate (cache); + + return G_SOURCE_REMOVE; +} + +void +gtk_text_line_display_cache_delay_eviction (GtkTextLineDisplayCache *cache) +{ + g_assert (cache != NULL); + + if (cache->evict_source != NULL) + { + gint64 deadline; + + deadline = g_get_monotonic_time () + (BLOW_CACHE_TIMEOUT_SEC * G_USEC_PER_SEC); + g_source_set_ready_time (cache->evict_source, deadline); + } + else + { + guint tag; + + tag = g_timeout_add_seconds (BLOW_CACHE_TIMEOUT_SEC, + gtk_text_line_display_cache_blow_cb, + cache); + cache->evict_source = g_main_context_find_source_by_id (NULL, tag); + g_source_set_name (cache->evict_source, "[gtk+] gtk_text_line_display_cache_blow_cb"); + } +} + +#if DEBUG_LINE_DISPLAY_CACHE +static void +check_disposition (GtkTextLineDisplayCache *cache, + GtkTextLayout *layout) +{ + GSequenceIter *iter; + gint last = G_MAXUINT; + + g_assert (cache != NULL); + g_assert (cache->sorted_by_line != NULL); + g_assert (cache->line_to_display != NULL); + + for (iter = g_sequence_get_begin_iter (cache->sorted_by_line); + !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) + { + GtkTextLineDisplay *display = g_sequence_get (iter); + GtkTextIter text_iter; + guint line; + + gtk_text_layout_get_iter_at_line (layout, &text_iter, display->line, 0); + line = gtk_text_iter_get_line (&text_iter); + + g_assert_cmpint (line, >, last); + + last = line; + } +} +#endif + +static void +gtk_text_line_display_cache_take_display (GtkTextLineDisplayCache *cache, + GtkTextLineDisplay *display, + GtkTextLayout *layout) +{ + g_assert (cache != NULL); + g_assert (display != NULL); + g_assert (display->line != NULL); + g_assert (display->cache_iter == NULL); + g_assert (display->mru_link.data == display); + g_assert (display->mru_link.prev == NULL); + g_assert (display->mru_link.next == NULL); + g_assert (g_hash_table_lookup (cache->line_to_display, display->line) == NULL); + +#if DEBUG_LINE_DISPLAY_CACHE + check_disposition (cache, layout); +#endif + + display->cache_iter = + g_sequence_insert_sorted (cache->sorted_by_line, + display, + (GCompareDataFunc) gtk_text_line_display_compare, + layout); + g_hash_table_insert (cache->line_to_display, display->line, display); + g_queue_push_head_link (&cache->mru, &display->mru_link); + + /* Cull the cache if we're at capacity */ + while (cache->mru.length > MRU_MAX_SIZE) + { + display = g_queue_peek_tail (&cache->mru); + + gtk_text_line_display_cache_invalidate_display (cache, display, FALSE); + } +} + +/* + * gtk_text_line_display_cache_invalidate_display: + * @cache: a GtkTextLineDisplayCache + * @display: a GtkTextLineDisplay + * @cursors_only: if only the cursor positions should be invalidated + * + * If @cursors_only is TRUE, then only the cursors are invalidated. Otherwise, + * @display is removed from the cache. + * + * Use this function when you already have access to a display as it reduces + * some overhead. + */ +void +gtk_text_line_display_cache_invalidate_display (GtkTextLineDisplayCache *cache, + GtkTextLineDisplay *display, + gboolean cursors_only) +{ + g_assert (cache != NULL); + g_assert (display != NULL); + g_assert (display->line != NULL); + + if (cursors_only) + { + g_clear_pointer (&display->cursors, g_array_unref); + display->cursors_invalid = TRUE; + display->has_block_cursor = FALSE; + } + else + { + GSequenceIter *iter = g_steal_pointer (&display->cache_iter); + + if (cache->cursor_line == display->line) + cache->cursor_line = NULL; + + g_hash_table_remove (cache->line_to_display, display->line); + g_queue_unlink (&cache->mru, &display->mru_link); + + if (iter != NULL) + g_sequence_remove (iter); + } + + STAT_INC (cache->inval); +} + +/* + * gtk_text_line_display_cache_get: + * @cache: a #GtkTextLineDisplayCache + * @layout: a GtkTextLayout + * @line: a GtkTextLine + * @size_only: if only line sizing is needed + * + * Gets a GtkTextLineDisplay for @line. + * + * If no cached display exists, a new display will be created. + * + * It's possible that calling this function will cause some existing + * cached displays to be released and destroyed. + * + * Returns: (transfer full) (not nullable): a #GtkTextLineDisplay + */ +GtkTextLineDisplay * +gtk_text_line_display_cache_get (GtkTextLineDisplayCache *cache, + GtkTextLayout *layout, + GtkTextLine *line, + gboolean size_only) +{ + GtkTextLineDisplay *display; + + g_assert (cache != NULL); + g_assert (layout != NULL); + g_assert (line != NULL); + + display = g_hash_table_lookup (cache->line_to_display, line); + + if (display != NULL) + { + if (size_only || !display->size_only) + { + STAT_INC (cache->hits); + + if (!size_only && display->line == cache->cursor_line) + gtk_text_layout_update_display_cursors (layout, display->line, display); + + /* Move to front of MRU */ + g_queue_unlink (&cache->mru, &display->mru_link); + g_queue_push_head_link (&cache->mru, &display->mru_link); + + return gtk_text_line_display_ref (display); + } + + /* We need an updated display that includes more than just + * sizing, so we need to drop this entry and force the layout + * to create a new one. + */ + gtk_text_line_display_cache_invalidate_display (cache, display, FALSE); + } + + STAT_INC (cache->misses); + + g_assert (!g_hash_table_lookup (cache->line_to_display, line)); + + display = gtk_text_layout_create_display (layout, line, size_only); + + g_assert (display != NULL); + g_assert (display->line == line); + + if (!size_only) + { + if (line == cache->cursor_line) + gtk_text_layout_update_display_cursors (layout, line, display); + + gtk_text_line_display_cache_take_display (cache, + gtk_text_line_display_ref (display), + layout); + } + + return g_steal_pointer (&display); +} + +void +gtk_text_line_display_cache_invalidate (GtkTextLineDisplayCache *cache) +{ + g_assert (cache != NULL); + g_assert (cache->sorted_by_line != NULL); + g_assert (cache->line_to_display != NULL); + + STAT_ADD (cache->inval, g_hash_table_size (cache->line_to_display)); + + cache->cursor_line = NULL; + + while (cache->mru.head != NULL) + { + GtkTextLineDisplay *display = g_queue_peek_head (&cache->mru); + + gtk_text_line_display_cache_invalidate_display (cache, display, FALSE); + } + + g_assert (g_hash_table_size (cache->line_to_display) == 0); + g_assert (g_sequence_get_length (cache->sorted_by_line) == 0); + g_assert (cache->mru.length == 0); +} + +void +gtk_text_line_display_cache_invalidate_cursors (GtkTextLineDisplayCache *cache, + GtkTextLine *line) +{ + GtkTextLineDisplay *display; + + g_assert (cache != NULL); + g_assert (line != NULL); + + STAT_INC (cache->inval_cursors); + + display = g_hash_table_lookup (cache->line_to_display, line); + + if (display != NULL) + gtk_text_line_display_cache_invalidate_display (cache, display, TRUE); +} + +/* + * gtk_text_line_display_cache_invalidate_line: + * @self: a GtkTextLineDisplayCache + * @line: a GtkTextLine + * + * Removes a cached display for @line. + * + * Compare to gtk_text_line_display_cache_invalidate_cursors() which + * only invalidates the cursors for this row. + */ +void +gtk_text_line_display_cache_invalidate_line (GtkTextLineDisplayCache *cache, + GtkTextLine *line) +{ + GtkTextLineDisplay *display; + + g_assert (cache != NULL); + g_assert (line != NULL); + + display = g_hash_table_lookup (cache->line_to_display, line); + + if (display != NULL) + gtk_text_line_display_cache_invalidate_display (cache, display, FALSE); + + STAT_INC (cache->inval_by_line); +} + +static GSequenceIter * +find_iter_at_text_iter (GtkTextLineDisplayCache *cache, + GtkTextLayout *layout, + const GtkTextIter *iter) +{ + GSequenceIter *left; + GSequenceIter *right; + GSequenceIter *mid; + GSequenceIter *end; + GtkTextLine *target; + guint target_lineno; + + g_assert (cache != NULL); + g_assert (iter != NULL); + + if (g_sequence_is_empty (cache->sorted_by_line)) + return NULL; + + /* gtk_text_iter_get_line() will have cached value */ + target_lineno = gtk_text_iter_get_line (iter); + target = _gtk_text_iter_get_text_line (iter); + + /* Get some iters so we can work with pointer compare */ + end = g_sequence_get_end_iter (cache->sorted_by_line); + left = g_sequence_get_begin_iter (cache->sorted_by_line); + right = g_sequence_iter_prev (end); + + /* We already checked for empty above */ + g_assert (!g_sequence_iter_is_end (left)); + g_assert (!g_sequence_iter_is_end (right)); + + for (;;) + { + GtkTextLineDisplay *display; + guint lineno; + + if (left == right) + mid = left; + else + mid = g_sequence_range_get_midpoint (left, right); + + g_assert (mid != NULL); + g_assert (!g_sequence_iter_is_end (mid)); + + if (mid == end) + break; + + display = g_sequence_get (mid); + + g_assert (display != NULL); + g_assert (display->line != NULL); + g_assert (display->cache_iter != NULL); + + if (target == display->line) + return mid; + + if (right == left) + break; + + lineno = _gtk_text_line_get_number (display->line); + + if (target_lineno < lineno) + right = mid; + else if (target_lineno > lineno) + left = g_sequence_iter_next (mid); + else + g_assert_not_reached (); + } + + return NULL; +} + + +/* + * gtk_text_line_display_cache_invalidate_range: + * @cache: a GtkTextLineDisplayCache + * @begin: the starting text iter + * @end: the ending text iter + * + * Removes all GtkTextLineDisplay that fall between or including + * @begin and @end. + */ +void +gtk_text_line_display_cache_invalidate_range (GtkTextLineDisplayCache *cache, + GtkTextLayout *layout, + const GtkTextIter *begin, + const GtkTextIter *end, + gboolean cursors_only) +{ + GSequenceIter *begin_iter; + GSequenceIter *end_iter; + GSequenceIter *iter; + + g_assert (cache != NULL); + g_assert (layout != NULL); + g_assert (begin != NULL); + g_assert (end != NULL); + + STAT_INC (cache->inval_by_range); + + /* Short-circuit, is_empty() is O(1) */ + if (g_sequence_is_empty (cache->sorted_by_line)) + return; + + /* gtk_text_iter_order() preserving const */ + if (gtk_text_iter_compare (begin, end) > 0) + { + const GtkTextIter *tmp = begin; + end = begin; + begin = tmp; + } + + /* Common case, begin/end on same line. Just try to find the line by + * line number and invalidate it alone. + */ + if G_LIKELY (_gtk_text_iter_same_line (begin, end)) + { + begin_iter = find_iter_at_text_iter (cache, layout, begin); + + if (begin_iter != NULL) + { + GtkTextLineDisplay *display = g_sequence_get (begin_iter); + + g_assert (display != NULL); + g_assert (display->line != NULL); + + gtk_text_line_display_cache_invalidate_display (cache, display, cursors_only); + } + + return; + } + + /* Find GSequenceIter containing GtkTextLineDisplay that correspond + * to each of the text positions. + */ + begin_iter = find_iter_at_text_iter (cache, layout, begin); + end_iter = find_iter_at_text_iter (cache, layout, end); + + /* Short-circuit if we found nothing */ + if (begin_iter == NULL && end_iter == NULL) + return; + + /* If nothing matches the end, we need to walk to the end of our + * cached displays. We know there is a non-zero number of items + * in the sequence at this point, so we can iter_prev() safely. + */ + if (end_iter == NULL) + end_iter = g_sequence_iter_prev (g_sequence_get_end_iter (cache->sorted_by_line)); + + /* If nothing matched the begin, we need to walk starting from + * the first display we have cached. + */ + if (begin_iter == NULL) + begin_iter = g_sequence_get_begin_iter (cache->sorted_by_line); + + iter = begin_iter; + + for (;;) + { + GtkTextLineDisplay *display = g_sequence_get (iter); + GSequenceIter *next = g_sequence_iter_next (iter); + + gtk_text_line_display_cache_invalidate_display (cache, display, cursors_only); + + if (iter == end_iter) + break; + + iter = next; + } +} + +static GSequenceIter * +find_iter_at_at_y (GtkTextLineDisplayCache *cache, + GtkTextLayout *layout, + gint y) +{ + GtkTextBTree *btree; + GSequenceIter *left; + GSequenceIter *right; + GSequenceIter *mid; + GSequenceIter *end; + + g_assert (cache != NULL); + g_assert (layout != NULL); + + if (g_sequence_is_empty (cache->sorted_by_line)) + return NULL; + + btree = _gtk_text_buffer_get_btree (layout->buffer); + + /* Get some iters so we can work with pointer compare */ + end = g_sequence_get_end_iter (cache->sorted_by_line); + left = g_sequence_get_begin_iter (cache->sorted_by_line); + right = g_sequence_iter_prev (end); + + /* We already checked for empty above */ + g_assert (!g_sequence_iter_is_end (left)); + g_assert (!g_sequence_iter_is_end (right)); + + for (;;) + { + GtkTextLineDisplay *display; + gint cache_y; + gint cache_height; + + if (left == right) + mid = left; + else + mid = g_sequence_range_get_midpoint (left, right); + + g_assert (mid != NULL); + g_assert (!g_sequence_iter_is_end (mid)); + + if (mid == end) + break; + + display = g_sequence_get (mid); + + g_assert (display != NULL); + g_assert (display->line != NULL); + + cache_y = _gtk_text_btree_find_line_top (btree, display->line, layout); + cache_height = display->height; + + if (y >= cache_y && y <= (cache_y + cache_height)) + return mid; + + if (left == right) + break; + + if (y < cache_y) + right = mid; + else if (y > (cache_y + cache_height)) + left = g_sequence_iter_next (mid); + else + g_assert_not_reached (); + } + + return NULL; +} + +/* + * gtk_text_line_display_cache_invalidate_y_range: + * @cache: a GtkTextLineDisplayCache + * @y: the starting Y position + * @old_height: the height to invalidate + * @cursors_only: if only cursors should be invalidated + * + * Remove all GtkTextLineDisplay that fall into the range starting + * from the Y position to Y+Height. + */ +void +gtk_text_line_display_cache_invalidate_y_range (GtkTextLineDisplayCache *cache, + GtkTextLayout *layout, + gint y, + gint old_height, + gboolean cursors_only) +{ + GSequenceIter *iter; + GtkTextBTree *btree; + + g_assert (cache != NULL); + g_assert (layout != NULL); + + STAT_INC (cache->inval_by_y_range); + + btree = _gtk_text_buffer_get_btree (layout->buffer); + iter = find_iter_at_at_y (cache, layout, y); + + if (iter == NULL) + return; + + while (!g_sequence_iter_is_end (iter)) + { + GtkTextLineDisplay *display; + gint cache_y; + gint cache_height; + + display = g_sequence_get (iter); + iter = g_sequence_iter_next (iter); + + cache_y = _gtk_text_btree_find_line_top (btree, display->line, layout); + cache_height = display->height; + + if (cache_y + cache_height > y && cache_y < y + old_height) + { + gtk_text_line_display_cache_invalidate_display (cache, display, cursors_only); + + y += cache_height; + old_height -= cache_height; + + if (old_height > 0) + continue; + } + + break; + } +} + +void +gtk_text_line_display_cache_set_cursor_line (GtkTextLineDisplayCache *cache, + GtkTextLine *cursor_line) +{ + GtkTextLineDisplay *display; + + g_assert (cache != NULL); + + if (cursor_line == cache->cursor_line) + return; + + display = g_hash_table_lookup (cache->line_to_display, cache->cursor_line); + + if (display != NULL) + gtk_text_line_display_cache_invalidate_display (cache, display, FALSE); + + cache->cursor_line = cursor_line; + + display = g_hash_table_lookup (cache->line_to_display, cache->cursor_line); + + if (display != NULL) + gtk_text_line_display_cache_invalidate_display (cache, display, FALSE); +} diff --git a/gtk/gtktextlinedisplaycacheprivate.h b/gtk/gtktextlinedisplaycacheprivate.h new file mode 100644 index 0000000000..5639ec2070 --- /dev/null +++ b/gtk/gtktextlinedisplaycacheprivate.h @@ -0,0 +1,59 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * Copyright (C) 2019 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef __GTK_TEXT_LINE_DISPLAY_CACHE_PRIVATE_H__ +#define __GTK_TEXT_LINE_DISPLAY_CACHE_PRIVATE_H__ + +#include "gtktextlayoutprivate.h" + +G_BEGIN_DECLS + +typedef struct _GtkTextLineDisplayCache GtkTextLineDisplayCache; + +GtkTextLineDisplayCache *gtk_text_line_display_cache_new (void); +void gtk_text_line_display_cache_free (GtkTextLineDisplayCache *cache); +GtkTextLineDisplay *gtk_text_line_display_cache_get (GtkTextLineDisplayCache *cache, + GtkTextLayout *layout, + GtkTextLine *line, + gboolean size_only); +void gtk_text_line_display_cache_delay_eviction (GtkTextLineDisplayCache *cache); +void gtk_text_line_display_cache_set_cursor_line (GtkTextLineDisplayCache *cache, + GtkTextLine *line); +void gtk_text_line_display_cache_invalidate (GtkTextLineDisplayCache *cache); +void gtk_text_line_display_cache_invalidate_cursors (GtkTextLineDisplayCache *cache, + GtkTextLine *line); +void gtk_text_line_display_cache_invalidate_display (GtkTextLineDisplayCache *cache, + GtkTextLineDisplay *display, + gboolean cursors_only); +void gtk_text_line_display_cache_invalidate_line (GtkTextLineDisplayCache *cache, + GtkTextLine *line); +void gtk_text_line_display_cache_invalidate_range (GtkTextLineDisplayCache *cache, + GtkTextLayout *layout, + const GtkTextIter *begin, + const GtkTextIter *end, + gboolean cursors_only); +void gtk_text_line_display_cache_invalidate_y_range (GtkTextLineDisplayCache *cache, + GtkTextLayout *layout, + gint y, + gint height, + gboolean cursors_only); + +G_END_DECLS + +#endif /* __GTK_TEXT_LINE_DISPLAY_CACHE_PRIVATE_H__ */ diff --git a/gtk/meson.build b/gtk/meson.build index 22fe73077f..cdf186af91 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -369,6 +369,7 @@ gtk_public_sources = files([ 'gtktexthandle.c', 'gtktextiter.c', 'gtktextlayout.c', + 'gtktextlinedisplaycache.c', 'gtktextmark.c', 'gtktextsegment.c', 'gtktexttag.c', -- 2.30.2